package com.dap.core.config;

import java.io.IOException;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.io.Resource;

import com.dap.configsvrclient.RegistryObserver;
import com.dap.core.context.ContextUtils;


public class ConfigurationLoader extends PropertyPlaceholderConfigurer implements BeanPostProcessor, PriorityOrdered, RegistryObserver {
    protected static final Logger logger = LoggerFactory.getLogger(ConfigurationLoader.class);
    private static final String INITIAL_PROPS_KEY = "mergedConfigProps";
    private Resource[] locations;
    private final Hashtable<String, Set<ConfigurationListener>> configListeners = new Hashtable<>();
    private final Map<String, Object> configs = new ConcurrentHashMap<>();


    @Override
    public void setLocation(Resource location) {
        setLocations(new Resource[] { location });
    }


    @Override
    public void setLocations(Resource... locations) {
        this.locations = locations;
        super.setLocations(locations);
    }


    public void addConfigListener(String configKey, ConfigurationListener configListener) {
        Set<ConfigurationListener> listeners = this.configListeners.get(configKey);
        if (listeners != null) {
            listeners.add(configListener);
        } else {
            listeners = new HashSet<>();
            listeners.add(configListener);
            this.configListeners.put(configKey, listeners);
        }
    }


    public void notifyConfigListener(String configKey) {
        Set<ConfigurationListener> listeners = this.configListeners.get(configKey);
        if (listeners != null) {
            for (ConfigurationListener listener : listeners) {
                Object config = this.configs.get(configKey);
                if ((config != null) && ((config instanceof Resource))) {
                    listener.resourceChanged(configKey, (Resource) config);
                }
                if ((config != null) && ((config instanceof Properties))) {
                    Properties mergedConfigProps = (Properties) this.configs.get(INITIAL_PROPS_KEY);

                    Properties changedProps = getChangedProps((Properties) config, mergedConfigProps);

                    listener.configChanged(configKey, changedProps);
                }
            }
        }
    }


    private Properties getChangedProps(Properties updated, Properties orginal) {
        Properties changedProps = new Properties();
        for (Object key : updated.keySet()) {
            if ((orginal.get(key) != null) && (!orginal.get(key).equals(updated.get(key)))) {
                changedProps.put(key, updated.get(key));
            }
        }
        return changedProps;
    }


    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) throws BeansException {
        super.setSystemPropertiesMode(2);

        super.processProperties(beanFactoryToProcess, props);
        this.configs.put(INITIAL_PROPS_KEY, props);

        setToContext(props);
        if (isConfigServerEnabed()) {
            for (Resource r : this.locations) {
                if ((r instanceof ConfigSvrResource)) {
                    ConfigServerClientFactory.getConfigServerClient().register(r.getFilename(), null, true);
                }
            }
        }
    }


    private void setToContext(Properties mergedProps) {
        logger.info("DAP system properties:");
        for (Object key : mergedProps.keySet()) {
            logger.info(key + "=" + mergedProps.getProperty((String) key));
        }

        Properties configuration = ContextUtils.getDapContext().configuration;
        if (configuration == null) {
            configuration = new Properties();
        }
        configuration.putAll(mergedProps);
    }


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!isConfigServerEnabed()) {
            return bean;
        }
        ConfigurationListener listener;
        if ((bean instanceof ConfigurationListener)) {
            listener = (ConfigurationListener) bean;
            for (String configKey : listener.getConfigKeys()) {
                ConfigServerClientFactory.getConfigServerClient().subscribe(configKey, this);

                addConfigListener(configKey, listener);
            }
        }
        return bean;
    }


    @Override
    public int getOrder() {
        return Integer.MAX_VALUE;
    }


    @Override
    public void registryChanged(String configKey) {
        try {
            Resource res = new ConfigSvrResource(configKey);
            if (res != null) {
                if (res.getFilename().endsWith(".properties")) {
                    Properties props = new Properties();
                    props.load(res.getInputStream());
                    this.configs.put(configKey, props);
                } else {
                    this.configs.put(configKey, res);
                }
            }
        } catch (IOException e) {
            logger.error("ConfigSvrResource:[" + configKey + "] reload error", e);
        }
        notifyConfigListener(configKey);
        if ((this.configs.get(configKey) instanceof Properties)) {
            Properties updatedConfig = (Properties) this.configs.get(configKey);
            Properties newAllProps = (Properties) this.configs.get(INITIAL_PROPS_KEY);

            newAllProps.putAll(updatedConfig);
            setToContext(newAllProps);
        }
    }


    private boolean isConfigServerEnabed() {
        return BootstrapConfigUtil.isTrue(BootstrapConfigUtil.CONFIG_SERVER_ENABLE);
    }
}
